#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include "CompPermIdx.hpp"
#include "cmdline.h"

#include <time.h>
#include <sys/time.h>
#include <stdio.h>

using namespace std;
using namespace cpi00;

double gettimeofday_sec()
{
  struct timeval tv;
  gettimeofday(&tv, NULL);
  return tv.tv_sec + (double)tv.tv_usec*1e-6;
}

void PrintQuery(CompPermIdx& cpi,
                const string& option,
                const string& query) {
  if (option == "mb" || option == "membership") {
    cout << "mode:Membership" << endl;
    if (cpi.Membership(query)) {
      cout << "Found!" << endl;
    }
    else {
      cout << "NotFound!" << endl;
    }
    return;
  }
  
  if (option == "rk" || option == "rank") {
    cout << "mode:Rank" << endl;
    uint64_t i;
    if ((i = cpi.Rank(query)) != UINT_MAX) {
      cout << "Rank(" << query << ") =" << i << endl;
    }
    else {
      cout << "NotFound!" << endl;
    }
    return;
  }

  if (option == "sl" || option == "select") {
    cout << "mode:Select" << endl;
    stringstream ss;
    uint64_t i;
    ss << query;
    ss >> i;
    string ret;
    cpi.Select(i, ret);
    cout << "Select(" << i << ") = " << ret << endl;
    return;
  }

  vector<string> ret;
  if (option == "pf" || option == "prefix") {
    cout << "mode:PrefixSearch" << endl;
    cout << "prefix = " << query << endl;
    cpi.PrefixSearch(query, ret);
  }
  else if (option == "sf" || option == "suffix") { 
    cout << "mode:SuffixSearch" << endl;
    cout << "suffix = " << query << endl;
    cpi.SuffixSearch(query, ret);
  }
  else if (option == "ps" || option == "prefixsuffix") {
    stringstream ss;
    string prefix, suffix;
    ss << query;
    ss >> prefix >> suffix;
    cout << "mode:PrefixSuffixSearch " << endl;
    cout << "prefix = " << prefix << endl;
    cout << "suffix = " << suffix << endl;
    cpi.PrefixSuffixSearch(prefix, suffix, ret);
  }
  else if (option == "ss" || option == "substring") {
    cout << "mode:SubstringSearch " << endl;
    cout << "substring = " << query << endl;
    cpi.SubstringSearch(query, ret);
  }
  else {
    cout << "OptionError!" << endl;
    return;
  }
  
  if (ret.size() == 0) {
    cout << "NotFound!" << endl;
  }
  else {
    for (uint64_t i = 0; i != ret.size(); ++i) {
      cout << " " << ret[i] << endl;
    }
    cout << "#hits = " << ret.size() << endl;
  }
}

int ReadKeyList(const string& fn, vector<string>& key_list){
  fstream ifs(fn.c_str());
  if (!ifs){
    cerr << "cannot open " << fn << endl;
    return -1;
  }

  for (string key; getline(ifs, key); ) {
    if (key.size() > 0 &&
        key[key.size() - 1] == '\r') {
      key = key.substr(0, key.size() - 1);
    }
    key_list.push_back(key);
  }
  return 0;
}

void PerformanceTestCPI(CompPermIdx& cpi, vector<string>& key_list) {
  random_shuffle(key_list.begin(), key_list.end());

  {
    cout << "start membership query" << endl;
    double start = gettimeofday_sec();
    for (uint64_t i = 0; i < key_list.size() && i < 10000; ++i){
      if (!cpi.Membership(key_list[i])) { 
        cerr << "Error occurred!" << endl;
      }
    }
    double end = gettimeofday_sec();
    cout << "  query time:\t" << end - start << endl; 
    cout << "  check keys:\t" << min((int)key_list.size(), 10000) << endl;
    cout << endl;
  }
  {
    cout << "start select query" << endl;
    double start = gettimeofday_sec();
    for (uint64_t i = 0; i < key_list.size() && i < 10000; ++i){
      string dummy;
      cpi.Select(rand()%cpi.NumKeys() + 1, dummy);
    }
    double end = gettimeofday_sec();
    cout << "  query time:\t" << end - start << endl; 
    cout << "  check keys:\t" << min((int)key_list.size(), 10000) << endl;
    cout << endl;
  }
  {
    cout << "start prefix search query" << endl;
    uint64_t num_hit = 0;
    double start = gettimeofday_sec();
    for (uint64_t i = 0; i < key_list.size() && i < 10000; ++i){
      vector<string> dummy;
      cpi.PrefixSearch(key_list[i].substr(0, 3), dummy);
      if (dummy.size() == 0) {
        cerr << "Error occurred!" << endl;
      }
      num_hit += dummy.size();
    }
    double end = gettimeofday_sec();
    cout << "  query time:\t" << end - start << endl; 
    cout << "  check keys:\t" << min((int)key_list.size(), 10000) << endl;
    cout << "  hit keys:\t"   << num_hit << endl;
    cout << endl;
  }
}

int BuildCPI(const string& fn, const string& index, const bool test) {
  vector<string> key_list;
  if (ReadKeyList(fn, key_list) == -1) {
    return -1;
  }
  CompPermIdx cpi;
  double start = gettimeofday_sec();
  cpi.Build(key_list);
  double elapsed_time = gettimeofday_sec() - start;
  cout << "  index time:\t" << elapsed_time << " sec" << endl;

  if (index == "") return 0;
  ofstream ofs(index.c_str());
  uint64_t bytes = cpi.Write(ofs);
  cout << "  index size:\t" << bytes << " bytes" << endl;

  if (test) {
    PerformanceTestCPI(cpi, key_list);
  }

  return 0;
}

void PrintOption() {
  cout << endl;
  cout << "---Help---" << endl;
  cout << "mode > \"mb\" or \"membership\"   : Membership query" << endl;
  cout << " query > string" << endl;
  cout << "mode > \"rk\" or \"rank\"         : Rank query" << endl;
  cout << " query > string" << endl;
  cout << "mode > \"sl\" or \"select\"       : Select query" << endl;
  cout << " query > integer" << endl;
  cout << "mode > \"pf\" or \"prefix\"       : Prefix search" << endl;
  cout << " query > prefix" << endl;
  cout << "mode > \"sf\" or \"suffix\"       : Suffix search" << endl;
  cout << " query > suffix" << endl;
  cout << "mode > \"ps\" or \"prefixsuffix\" : PrefixSuffix search" << endl;
  cout << " query > prefix suffix" << endl;
  cout << "mode > \"ss\" or \"substring\"    : Substring search" << endl;
  cout << " query > substring" << endl;
  cout << "mode > \"q\" or \"quit\"          : Quit" << endl;
  cout << "mode > \"h\" or \"help\"          : Show this message" << endl;
  cout << "----------" << endl;
  cout << endl;
}

int SearchCPI(const string& index) {
  fstream ofs(index.c_str());
  cpi00::CompPermIdx cpi;
  cpi.Read(ofs);
  cout << "read:" << cpi.NumKeys() << " keys" << endl;
  
  string option;
  string query;
  for (;;) {
    cout << "mode > ";
    getline(cin, option);
    if (option.size() == 0 || 
        option == "h" || option == "help") {
      PrintOption();
      continue;
    }
    if (option == "q" || option == "quit") {
      break;
    }
    cout << "query > ";
    getline(cin, query);
    if (query.size() == 0) {
      continue;
    }
    PrintQuery(cpi, option, query);
    cout << endl;
  }
  return 0;
}

int ShowKeysCPI(const string& index) {
  CompPermIdx cpi;
  ifstream ifs(index.c_str());
  cpi.Read(ifs);
  for (uint64_t i = 1; i <= cpi.NumKeys(); ++i) {
    string ret;
    cpi.Select(i, ret);
    cout << i << "\t:" << ret <<  endl;
  }
  return 0;
}

int main(int argc, char* argv[]) {
  cmdline::parser p;
  p.add<string>("keylist",   'k', "key list", false);
  p.add<string>("index",     'i', "index",    true);
  p.add        ("test",      't', "performance test");
  p.add        ("show_keys", 's', "show all keys");
  p.add        ("help",      'h', "this message");
  p.set_program_name("cpi00");

  if (!p.parse(argc, argv) || p.exist("help")) {
    cerr << p.usage() << endl;
    return -1;
  }

  if (p.exist("keylist")) {
    return BuildCPI(p.get<string>("keylist"), 
                    p.get<string>("index"),
                    p.exist("test"));
  }
  else if (p.exist("show_keys")) {
    return ShowKeysCPI(p.get<string>("index"));
  }
  else {
    return SearchCPI(p.get<string>("index"));
  }

  return 0;
}
